import {
  WebSocketGateway,
  WebSocketServer,
  SubscribeMessage,
  MessageBody,
  ConnectedSocket,
  OnGatewayConnection,
  OnGatewayDisconnect,
} from '@nestjs/websockets';
import { Server, Socket } from 'socket.io';
import { Injectable, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { ScriptExecution } from '../database/entities/script-execution.entity';

@Injectable()
@WebSocketGateway({
  namespace: '/scripts',
  cors: {
    origin: '*',
  },
})
export class ScriptsGateway implements OnGatewayConnection, OnGatewayDisconnect {
  @WebSocketServer()
  server: Server;
  
  private readonly logger = new Logger(ScriptsGateway.name);
  private executionClients = new Map<number, Set<string>>();

  constructor(
    @InjectRepository(ScriptExecution)
    private executionRepository: Repository<ScriptExecution>,
  ) {}

  handleConnection(client: Socket) {
    this.logger.log(`Client connected: ${client.id}`);
  }

  handleDisconnect(client: Socket) {
    this.logger.log(`Client disconnected: ${client.id}`);
    // 清理客户端订阅
    this.executionClients.forEach((clients, executionId) => {
      clients.delete(client.id);
      if (clients.size === 0) {
        this.executionClients.delete(executionId);
      }
    });
  }

  @SubscribeMessage('subscribe-execution')
  async handleSubscribeExecution(
    @MessageBody() data: { executionId: number },
    @ConnectedSocket() client: Socket,
  ) {
    const { executionId } = data;
    
    // 验证 executionId
    if (!executionId || isNaN(executionId) || executionId <= 0) {
      this.logger.warn(`Client ${client.id} attempted to subscribe with invalid executionId: ${executionId}`);
      client.emit('subscription-error', { 
        error: 'Invalid execution ID',
        executionId 
      });
      return;
    }
    
    if (!this.executionClients.has(executionId)) {
      this.executionClients.set(executionId, new Set());
    }
    
    this.executionClients.get(executionId)!.add(client.id);
    
    this.logger.log(`Client ${client.id} subscribed to execution ${executionId}`);
    
    // 检查执行是否已经完成，如果是，发送已存在的输出
    await this.sendExistingOutputIfCompleted(executionId, client.id);
    
    client.emit('subscription-confirmed', { executionId });
  }

  @SubscribeMessage('unsubscribe-execution')
  handleUnsubscribeExecution(
    @MessageBody() data: { executionId: number },
    @ConnectedSocket() client: Socket,
  ) {
    const { executionId } = data;
    
    // 验证 executionId
    if (!executionId || isNaN(executionId) || executionId <= 0) {
      this.logger.warn(`Client ${client.id} attempted to unsubscribe with invalid executionId: ${executionId}`);
      return;
    }
    
    if (this.executionClients.has(executionId)) {
      this.executionClients.get(executionId)!.delete(client.id);
      
      if (this.executionClients.get(executionId)!.size === 0) {
        this.executionClients.delete(executionId);
      }
    }
    
    this.logger.log(`Client ${client.id} unsubscribed from execution ${executionId}`);
  }

  // 发送实时输出
  emitOutput(executionId: number, data: string) {
    const clients = this.executionClients.get(executionId);
    console.log(`[DEBUG] Emitting output for execution ${executionId}, clients: ${clients?.size || 0}`);
    if (clients && clients.size > 0) {
      clients.forEach((clientId) => {
        this.server.to(clientId).emit('execution-output', {
          executionId,
          data,
          timestamp: new Date().toISOString(),
        });
        console.log(`[DEBUG] Sent output to client ${clientId}: ${data}`);
      });
    } else {
      console.log(`[DEBUG] No clients subscribed to execution ${executionId}`);
    }
  }

  // 发送错误信息
  emitError(executionId: number, error: string) {
    const clients = this.executionClients.get(executionId);
    if (clients && clients.size > 0) {
      clients.forEach((clientId) => {
        this.server.to(clientId).emit('execution-error', {
          executionId,
          error,
          timestamp: new Date().toISOString(),
        });
      });
    }
  }

  // 发送状态更新
  emitStatusUpdate(executionId: number, status: string, exitCode?: number) {
    const clients = this.executionClients.get(executionId);
    if (clients && clients.size > 0) {
      clients.forEach((clientId) => {
        this.server.to(clientId).emit('execution-status', {
          executionId,
          status,
          exitCode,
          timestamp: new Date().toISOString(),
        });
      });
    }
  }

  // 检查执行是否已完成，如果是则发送已存在的输出
  private async sendExistingOutputIfCompleted(executionId: number, clientId: string) {
    try {
      const execution = await this.executionRepository.findOne({ 
        where: { id: executionId } 
      });
      
      if (!execution) {
        console.log(`[DEBUG] Execution ${executionId} not found in database`);
        return;
      }
      
      console.log(`[DEBUG] Found execution ${executionId}, status: ${execution.status}`);
      
      // 如果执行已完成或失败，发送已存在的输出
      if (execution.status === 'completed' || execution.status === 'failed') {
        console.log(`[DEBUG] Sending existing output to client ${clientId} for execution ${executionId}`);
        
        // 发送输出
        if (execution.output) {
          this.server.to(clientId).emit('execution-output', {
            executionId,
            data: execution.output,
            timestamp: new Date().toISOString(),
          });
          console.log(`[DEBUG] Sent existing output to client ${clientId}: ${execution.output}`);
        }
        
        // 发送错误（如果有）
        if (execution.error) {
          this.server.to(clientId).emit('execution-error', {
            executionId,
            error: execution.error,
            timestamp: new Date().toISOString(),
          });
          console.log(`[DEBUG] Sent existing error to client ${clientId}: ${execution.error}`);
        }
        
        // 发送最终状态
        this.server.to(clientId).emit('execution-status', {
          executionId,
          status: execution.status,
          exitCode: execution.exitCode,
          timestamp: execution.endTime ? execution.endTime.toISOString() : new Date().toISOString(),
        });
        console.log(`[DEBUG] Sent final status to client ${clientId}: ${execution.status}`);
      }
    } catch (error) {
      console.error(`[DEBUG] Error sending existing output for execution ${executionId}:`, error);
    }
  }
}